/***
 * Excerpted from "Seven Web Frameworks in Seven Weeks",
 * published by The Pragmatic Bookshelf.
 * Copyrights apply to this code. It may not be used to create training material, 
 * courses, books, articles, and the like. Contact us if you are in doubt.
 * We make no guarantees that this code is fit for any purpose. 
 * Visit http://www.pragmaticprogrammer.com/titles/7web for more book information.
***/
/*!
 * CanJS - 1.1.8
 * http://canjs.us/
 * Copyright (c) 2013 Bitovi
 * Tue, 24 Sep 2013 21:59:55 GMT
 * Licensed MIT
 * Includes: can/model/queue
 * Download from: http://canjs.com
 */
(function(can) {

    var cleanAttrs = function(changedAttrs, attrs) {
        var newAttrs = can.extend(true, {}, attrs),
            attr, current, path;
        if (changedAttrs) {
            // go through the attributes returned from the server
            // and remove those that were changed during the current
            // request batch
            for (var i = 0; i < changedAttrs.length; i++) {
                current = newAttrs;
                path = changedAttrs[i].split('.');
                while (path.length > 1) {
                    current = current && current[path.shift()];
                }
                current && delete current[path.shift()];
            }
        }
        return newAttrs;
    },
        queueRequests = function(success, error, method, callback) {
            this._changedAttrs = this._changedAttrs || [];

            var def = new can.Deferred,
                self = this,
                attrs = this.attr(),
                queue = this._requestQueue,
                changedAttrs = this._changedAttrs,
                reqFn, index;

            reqFn = (function(self, type, success, error) {
                // Function that performs actual request
                return function() {
                    // pass already serialized attributes because we want to 
                    // save model in state it was when request was queued, not
                    // when request is ran
                    return self.constructor._makeRequest([self, attrs], type || (self.isNew() ? 'create' : 'update'), success, error, callback)
                }
            })(this, method, function() {
                // resolve deferred with results from the request
                def.resolveWith(self, arguments);
                // remove current deferred from the queue 
                queue.splice(0, 1);
                if (queue.length > 0) {
                    // replace queued wrapper function with deferred
                    // returned from the makeRequest function so we 
                    // can access it's `abort` function
                    queue[0] = queue[0]();
                } else {
                    // clean up changed attrs since there is no more requests in the queue
                    changedAttrs.splice(0);
                }

            }, function() {
                // reject deferred with results from the request
                def.rejectWith(self, arguments);
                // since we failed remove all pending requests from the queue
                queue.splice(0);
                // clean up changed attrs since there is no more requests in the queue
                changedAttrs.splice(0);
            })

            // Add our fn to the queue
            index = queue.push(reqFn) - 1;

            // If there is only one request in the queue, run
            // it immediately.
            if (queue.length === 1) {
                // replace queued wrapper function with deferred
                // returned from the makeRequest function so we 
                // can access it's `abort` function
                queue[0] = queue[0]();
            }

            def.abort = function() {
                var abort;
                // check if this request is running, if it's not
                // just remove it from the queue
                // also all subsequent requests should be removed too
                abort = queue[index].abort && queue[index].abort();
                // remove aborted request and any requests after it
                queue.splice(index);
                // if there is no more requests in the queue clean up
                // the changed attributes array
                if (queue.length === 0) {
                    changedAttrs.splice(0);
                }
                return abort;
            }
            // deferred will be resolved with original success and
            // error functions
            def.then(success, error);

            return def;
        },
        _changes = can.Model.prototype._changes,
        destroyFn = can.Model.prototype.destroy,
        setupFn = can.Model.prototype.setup;

    can.each(["created", "updated", "destroyed"], function(fn) {
        var prototypeFn = can.Model.prototype[fn];

        can.Model.prototype[fn] = function(attrs) {
            if (attrs && typeof attrs == 'object') {
                attrs = attrs.attr ? attrs.attr() : attrs;
                // Create backup of last good known state returned
                // from the server. This allows users to restore it
                // if API returns error
                this._backupStore = attrs;
                attrs = cleanAttrs(this._changedAttrs || [], attrs);
            }
            // call the original function with the cleaned up attributes
            prototypeFn.call(this, attrs);
        }
    })

    can.extend(can.Model.prototype, {
            setup: function() {
                setupFn.apply(this, arguments);
                this._requestQueue = new can.Observe.List;
            },
            _changes: function(ev, attr, how, newVal, oldVal) {
                // record changes if there is a request running
                this._changedAttrs && this._changedAttrs.push(attr);
                _changes.apply(this, arguments);
            },
            hasQueuedRequests: function() {
                return this._requestQueue.attr('length') > 1;
            },
            // call queued save request
            save: function() {
                return queueRequests.apply(this, arguments);
            },
            destroy: function(success, error) {
                if (this.isNew()) {
                    // if it's a new instance, call default destroy method
                    return destroyFn.call(this, success, error);
                }
                return queueRequests.call(this, success, error, 'destroy', 'destroyed');
            }
        })

    return can;
})(can);